﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Infrastructure.ObjectModel;
using Infrastructure.ServiceInterfaces;

namespace B10_Ex05_Elad_Meyron.Models
{
    public class TexturedDradle : LoadableDrawableComponent
    {
        #region Data Members
        // Events
        public event DradleStoppedEventHandler DradleStoppedEvent;

        // Consts
        private const int k_NotFacingCamera = -10;
        private const int k_DradleWidth = 4;
        private const int k_DradleDepth = 4;
        private const int k_DradleHeight = 10;
        private const int k_DradleBodySize = 10;
        private const float k_SlowDown = 0.99f;
        private const float k_SlowSpeed = 0.3f;
        private const float k_SlowestSpeed = 0.2f;
        private const float k_SlowestDown = 0.999f;
        private const int k_NumFaces = 4;
        private const int k_LetterMargin = 1;

        // the 3d model and the shader:
        private BasicEffect m_BasicEffect;
        private VertexPositionTexture[] m_TopVertices;
        private VertexPositionTexture[] m_TopBodyVertices;
        private VertexPositionTexture[] m_HandleVertices;
        private VertexPositionTexture[] m_BodyVertices;
        private VertexPositionTexture[] m_BottomVertices;
        private int[] m_Indices;
        private IndexBuffer m_IndexBuffer;

        // Other members
        private VertexPositionTexture[,] m_Faces;
        private Vector3 m_Position = new Vector3(0, 0, 0);
        private Vector3 m_ForawrdVector = Vector3.Forward;
        private double m_currAngle = 0;
        private double m_cameraAngle = 0;
        public Vector3 m_Rotations = Vector3.Zero;
        private Vector3 m_Scales = Vector3.One;
        private Matrix m_WorldMatrix = Matrix.Identity;
        private World m_WorldData = null;
        private float m_RotationSpeed = 0;
        private Texture2D m_Texture;
        private VertexBuffer m_VertexBuffer;
        #endregion

        #region Ctor And Inits
        protected override void DrawBoundingBox()
        {
            // Not implemented
        }

        protected override void InitBounds()
        {
            RandomPosition();
        }

        private void RandomPosition()
        {
            // Get the utilty service
            UtilityService utl = (UtilityService)Game.Services.GetService(typeof(UtilityService));

            // Set random positions
            m_Position.X = utl.RandomNumber((int)m_WorldData.k_WorldSize / 2) * utl.RandomSign();
            m_Position.Y = utl.RandomNumber((int)m_WorldData.k_WorldSize / 4) * utl.RandomSign();
            m_Position.Z = -utl.RandomNumber((int)m_WorldData.k_WorldSize / 3);

            // Get the direction of the camera looking at the dradle

            // Get the other vectors ready, we are using X becacuse we are only
            // Intrested in the Z rotation angle
            Vector3 vXPos = new Vector3(m_Position.X, 0, 0);
            Vector3 vPos = Vector3.Backward;
            vPos.Normalize();

            // Get a vector from the camera to the dradle
            Matrix mtr = Matrix.CreateLookAt(m_WorldData.m_PointOfViewPosition, vXPos, Vector3.Up);
            Vector3 vCam = mtr.Forward;
            vCam.Normalize();

            // All dradles start angle is zero
            m_currAngle = 0;

            // Get the angle between the camera and the dradle
            m_cameraAngle = Math.Acos(Vector3.Dot(vPos, vCam));

            // If it's a straight line, the angle is 0
            if (double.IsNaN(m_cameraAngle))
            {
                m_cameraAngle = 0;
            }

            // Set to zero if it's very small too
            if (Math.Abs(m_cameraAngle) < 0.0001)
            {
                m_cameraAngle = 0;
            }
        }

        public TexturedDradle(Game i_game)
            : base(i_game)
        {
            m_WorldData = (World)i_game.Services.GetService(typeof(World));
            i_game.Components.Add(this);
        }

        #region Load Content
        protected override void LoadContent()
        {
            m_Faces = new VertexPositionTexture[k_NumFaces, 2];

            m_Texture = Game.Content.Load<Texture2D>(@"Letters_804x220");

            // we are working with the out-of-the box shader that comes with XNA:
            m_BasicEffect = new BasicEffect(this.GraphicsDevice, null);

            m_BasicEffect.Texture = m_Texture;
            m_BasicEffect.TextureEnabled = true;

            // we did not use certain clockwise ordering in our vertex buffer
            // and we don't want antthing to be culled away..
            this.GraphicsDevice.RenderState.CullMode = CullMode.None;

            this.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            this.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            this.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            m_BasicEffect.View = m_WorldData.PointOfView;
            m_BasicEffect.Projection = m_WorldData.ProjectionFieldOfView;
            m_BasicEffect.Alpha = 1;
            m_BasicEffect.AmbientLightColor = Vector3.One;
            m_BasicEffect.EmissiveColor = Vector3.One;
            m_BasicEffect.FogEnabled = false;
            m_BasicEffect.LightingEnabled = true;
            m_BasicEffect.PreferPerPixelLighting = true;

            // Create handle vertices
            m_HandleVertices = new VertexPositionTexture[10];
            m_HandleVertices[0] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleWidth / 2), m_Position.Y, m_Position.Z - (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[1] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleWidth / 2), m_Position.Y + k_DradleHeight, m_Position.Z - (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[2] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleWidth / 2), m_Position.Y, m_Position.Z - (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[3] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleWidth / 2), m_Position.Y + k_DradleHeight, m_Position.Z - (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[4] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleWidth / 2), m_Position.Y, m_Position.Z + (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[5] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleWidth / 2), m_Position.Y + k_DradleHeight, m_Position.Z + (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[6] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleWidth / 2), m_Position.Y, m_Position.Z + (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[7] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleWidth / 2), m_Position.Y + k_DradleHeight, m_Position.Z + (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[8] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleWidth / 2), m_Position.Y, m_Position.Z - (k_DradleDepth / 2)), Vector2.One);
            m_HandleVertices[9] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleWidth / 2), m_Position.Y + k_DradleHeight, m_Position.Z - (k_DradleDepth / 2)), Vector2.One);

            // Create top vertices
            m_TopVertices = new VertexPositionTexture[4];
            m_TopVertices[0] = m_HandleVertices[1];
            m_TopVertices[1] = m_HandleVertices[7];
            m_TopVertices[2] = m_HandleVertices[3];
            m_TopVertices[3] = m_HandleVertices[5];

            // Create Body Vertices
            m_BodyVertices = new VertexPositionTexture[10];
            m_BodyVertices[0] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleBodySize / 2), m_Position.Y - k_DradleHeight, m_Position.Z - (k_DradleBodySize / 2)), new Vector2(0, 1));
            m_BodyVertices[1] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleBodySize / 2), m_Position.Y, m_Position.Z - (k_DradleBodySize / 2)), new Vector2(0, 0));
            m_BodyVertices[2] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleBodySize / 2), m_Position.Y - k_DradleHeight, m_Position.Z - (k_DradleBodySize / 2)), new Vector2(0.25f, 1));
            m_BodyVertices[3] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleBodySize / 2), m_Position.Y, m_Position.Z - (k_DradleBodySize / 2)), new Vector2(0.25f, 0));
            m_BodyVertices[4] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleBodySize / 2), m_Position.Y - k_DradleHeight, m_Position.Z + (k_DradleBodySize / 2)), new Vector2(0.5f, 1));
            m_BodyVertices[5] = new VertexPositionTexture(new Vector3(m_Position.X - (k_DradleBodySize / 2), m_Position.Y, m_Position.Z + (k_DradleBodySize / 2)), new Vector2(0.5f, 0));
            m_BodyVertices[6] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleBodySize / 2), m_Position.Y - k_DradleHeight, m_Position.Z + (k_DradleBodySize / 2)), new Vector2(0.75f, 1));
            m_BodyVertices[7] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleBodySize / 2), m_Position.Y, m_Position.Z + (k_DradleBodySize / 2)), new Vector2(0.75f, 0));
            m_BodyVertices[8] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleBodySize / 2), m_Position.Y - k_DradleHeight, m_Position.Z - (k_DradleBodySize / 2)), new Vector2(1, 1));
            m_BodyVertices[9] = new VertexPositionTexture(new Vector3(m_Position.X + (k_DradleBodySize / 2), m_Position.Y, m_Position.Z - (k_DradleBodySize / 2)), new Vector2(1, 0));

            // lets create a vertex buffer that will be stored in the graphic device:
            m_VertexBuffer = new VertexBuffer(
                this.GraphicsDevice,
                VertexPositionTexture.SizeInBytes * m_BodyVertices.Length, // the total size of the buffer
                BufferUsage.WriteOnly);

            m_VertexBuffer.SetData<VertexPositionTexture>(m_BodyVertices, 0, m_BodyVertices.Length);

            // build index buffer
            // lets create our 6 indices, which will be used to create 2 triangles (a rectangle):
            m_Indices = new int[25];
            for (int i = 0; i < (m_Indices.Length / 2) - 2; i++)
            {
                m_Indices[i * 3] = i;

                if ((i * 3) + 1 >= m_Indices.Length)
                {
                    break;
                }

                m_Indices[(i * 3) + 1] = i + 1;
                m_Indices[(i * 3) + 2] = i + 2;
            }

            // lets create an index buffer that will be stored in the graphic device as well:
            m_IndexBuffer = new IndexBuffer(
                this.GraphicsDevice,
                typeof(int),
                m_Indices.Length,
                BufferUsage.WriteOnly);

            m_IndexBuffer.SetData<int>(m_Indices);

            // Save Face vertices
            m_Faces[0, 0] = m_BodyVertices[4];
            m_Faces[0, 1] = m_BodyVertices[6];
            m_Faces[1, 0] = m_BodyVertices[6];
            m_Faces[1, 1] = m_BodyVertices[0];
            m_Faces[2, 0] = m_BodyVertices[0];
            m_Faces[2, 1] = m_BodyVertices[2];
            m_Faces[3, 0] = m_BodyVertices[2];
            m_Faces[3, 1] = m_BodyVertices[4];

            // Create Top-Body Vertices
            CreateTopBody();
            CreateBottom();
            
            base.LoadContent();
        }

        private void CreateTopBody()
        {
            m_TopBodyVertices = new VertexPositionTexture[4];
            m_TopBodyVertices[0] = m_BodyVertices[1];
            m_TopBodyVertices[1] = m_BodyVertices[7];
            m_TopBodyVertices[2] = m_BodyVertices[3];
            m_TopBodyVertices[3] = m_BodyVertices[5];
        }

        private void CreateBottom()
        {
            // Bottom vertices
            m_BottomVertices = new VertexPositionTexture[6];
            m_BottomVertices[0] = new VertexPositionTexture(
                new Vector3(
                    m_Position.X,
                                                                       m_Position.Y - (k_DradleBodySize * 2),
                                                                       m_Position.Z), 
                                                                       Vector2.One);
            m_BottomVertices[1] = m_BodyVertices[0];
            m_BottomVertices[2] = m_BodyVertices[2];
            m_BottomVertices[3] = m_BodyVertices[4];
            m_BottomVertices[4] = m_BodyVertices[6];
            m_BottomVertices[5] = m_BodyVertices[0];
        }

        #endregion
        #endregion

        public int CheckIfFacesUser(out float oDegreesToNextFace)
        {
            // Init
            oDegreesToNextFace = 0;

            // Get the dradle angles
            int nDradleAngle = (int)MathHelper.ToDegrees((float)m_currAngle);

            // If we are on the left of the screen the angle is reversed
            if (m_Position.X < 0)
            {
                nDradleAngle = (int)MathHelper.ToDegrees(MathHelper.TwoPi) - nDradleAngle;
            }

            // Get the camera angles
            int nCamera = (int)MathHelper.ToDegrees((float)m_cameraAngle);

            // Check if the angle is exactly against the camera
            if ((nCamera - nDradleAngle) % MathHelper.ToDegrees(MathHelper.PiOver2) == 0)
            {
                return (int)((nCamera - nDradleAngle) / MathHelper.ToDegrees(MathHelper.PiOver2));
            }
            else
            {
                oDegreesToNextFace = MathHelper.ToRadians(((nCamera - nDradleAngle) % MathHelper.ToDegrees(MathHelper.PiOver2)));
                return k_NotFacingCamera;
            }
        }

        protected override void UnloadContent()
        {
            if (m_BasicEffect != null)
            {
                m_BasicEffect.Dispose();
                m_BasicEffect = null;
            }
        }

        public void Rotate(float iRotationSpeed)
        {
            m_RotationSpeed = iRotationSpeed;
        }

        public override void Update(GameTime gameTime)
        {
            m_currAngle += (m_RotationSpeed * gameTime.ElapsedGameTime.Milliseconds) / 100;
            m_currAngle = m_currAngle % MathHelper.TwoPi;

            BuildWorldMatrix();

            float fClosingSpeed;

            // Check if we are near stop
            if (m_RotationSpeed > k_SlowSpeed)
            {
                // Slow down
                m_RotationSpeed *= k_SlowDown;
            }
            // Check if we can stop now
            else if (m_RotationSpeed > 0)
            {
                // Check if we are facing the camera
                int nFaceCamera = CheckIfFacesUser(out fClosingSpeed);

                // Check what happend
                if (nFaceCamera != k_NotFacingCamera)
                {
                    // We have reached the destination, stop the dradle please
                    m_RotationSpeed = 0;

                    // If someone want's to know we stopped
                    if (DradleStoppedEvent != null)
                    {
                        DradleStoppedEvent(
                            this,
                                           new DradleStoppedEventArgs { LetterStoppedOn = LetterFromIndex(nFaceCamera) });
                    }
                }
                // Slow down
                else
                {
                    // Only if we can slow down more
                    if (m_RotationSpeed > k_SlowestSpeed)
                    {
                        m_RotationSpeed *= k_SlowestDown;
                    }
                }
            }

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            this.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                this.GraphicsDevice, VertexPositionTexture.VertexElements);

            m_BasicEffect.World = m_WorldMatrix;

            m_BasicEffect.Begin();

            foreach (EffectPass pass in m_BasicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                m_BasicEffect.EnableDefaultLighting();

                this.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                    PrimitiveType.TriangleStrip, m_TopVertices, 0, 2);

                this.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                    PrimitiveType.TriangleStrip, m_HandleVertices, 0, 8);
                
                this.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                    PrimitiveType.TriangleStrip, m_TopBodyVertices, 0, 2);

                this.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                    PrimitiveType.TriangleFan, m_BottomVertices, 0, 4);

                this.GraphicsDevice.Vertices[0].SetSource(m_VertexBuffer, 0, VertexPositionTexture.SizeInBytes);
                this.GraphicsDevice.Indices = m_IndexBuffer;
                this.GraphicsDevice.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList,
                    0,
                    0,
                    m_BodyVertices.Length * 2,
                    0,
                    16);

                pass.End();
            }

            m_BasicEffect.End();

            base.Draw(gameTime);
        }

        private DradleLetters LetterFromIndex(int i_IndexToCheck)
        {
            if (m_Position.X >= 0 && m_Position.Y >= 0)
            {
                if ((i_IndexToCheck == 2) || (i_IndexToCheck == -2))
                {
                    return DradleLetters.H;
                }
                else if ((i_IndexToCheck == 3) || (i_IndexToCheck == -1))
                {
                    return DradleLetters.P;
                }
                else if (i_IndexToCheck == 4 || i_IndexToCheck == 0)
                {
                    return DradleLetters.N;
                }
                else if (i_IndexToCheck == 1)
                {
                    return DradleLetters.G;
                }
            }
            else
            {
                if (i_IndexToCheck == -2)
                {
                    return DradleLetters.H;
                }
                else if (i_IndexToCheck == 1)
                {
                    return DradleLetters.P;
                }
                else if (i_IndexToCheck == 0)
                {
                    return DradleLetters.N;
                }
                else if (i_IndexToCheck == -1)
                {
                    return DradleLetters.G;
                }
            }

            return DradleLetters.ERROR;
        }

        private void BuildWorldMatrix()
        {
            m_WorldMatrix =
                /*I*/ Matrix.Identity *
                /*S*/ Matrix.CreateScale(m_Scales) *
                /*R*/ Matrix.CreateRotationX(m_Rotations.X) *
                        Matrix.CreateRotationY((float)m_currAngle) *
                        Matrix.CreateRotationZ(m_Rotations.Z) *
                /* No Orbit */
                /*T*/ Matrix.CreateTranslation(m_Position);
        }
    }
}
